//
// Model.cs
//
// Copyright (C) 2006 Alp Toker <alp@atoker.com>
// Copyright (C) 2008 Lukas Lipka <lukaslipka@gmail.com>
//

// Based on http://people.freedesktop.org/~krh/akamaru.git
//
// References:
//   * http://en.wikipedia.org/wiki/Verlet_integration
//   * http://www.teknikus.dk/tj/gdc2001.htm

using System;
using System.Collections.Generic;

namespace Dashboard.Mint.Physics
{
	public class Model : IConstraint
	{
		public List<Particle> Particles = new List<Particle> ();
		public List<IConstraint> Constraints = new List<IConstraint> ();

		public List<Spacer> Spacers = new List<Spacer> ();
		public List<StringLink> StringLinks = new List<StringLink> ();
		public List<Stick> Sticks = new List<Stick> ();
		public List<Spring> Springs = new List<Spring> ();
		public List<Anchor> Anchors = new List<Anchor> ();
		public List<Polygon> Polygons = new List<Polygon> ();
		public List<Offset> Offsets = new List<Offset> ();
		public List<OffsetSpring> OffsetSprings = new List<OffsetSpring> ();

		public double K;
		public double Friction;
		public double Elasticity;
		public Vector Gravity;
		public int ConstrainIterations;

		public double Theta;

		public void Add (Particle n)
		{
		}

		public void AccumulateForces ()
		{
			foreach (Particle n in Particles)
				n.AccumulateForces (this);

			foreach (Spring s in Springs)
				s.AccumulateForces (this);

			foreach (OffsetSpring s in OffsetSprings)
				s.AccumulateForces (this);
		}

		public void Constrain ()
		{
			foreach (IConstraint c in Constraints)
				c.Constrain ();

			/*
			foreach (Anchor a in Anchors)
				a.Constrain ();

			foreach (StringLink s in StringLinks)
				s.Constrain ();

			foreach (Spacer s in Spacers)
				s.Constrain ();

			foreach (Stick s in Sticks)
				s.Constrain ();

			foreach (Offset o in Offsets)
				o.Constrain ();

			foreach (Polygon p in Polygons)
				p.Constrain ();
			*/

			/*
			foreach (Particle n in Particles)
				foreach (Polygon p in Polygons)
					p.Constrain (n); //wrong way round
			*/
		}

		public void Step (double delta_t)
		{
			AccumulateForces ();

			foreach (Particle n in Particles)
				n.Integrate (delta_t);

			for (int i = 0 ; i != ConstrainIterations ; i++)
				Constrain ();

			Theta += delta_t;
		}

		public Particle FindNearest (Point pos)
		{
			Particle nearest = null;
			double nearest_distance = Double.MaxValue;

			foreach (Particle n in Particles) {
				double distance = n.GetDistance (pos);

				if (nearest == null || distance < nearest_distance) {
					nearest_distance = distance;
					nearest = n;
				}
			}

			return nearest;
		}
	}

	public class Stick : IConstraint
	{
		public Particle A;
		public Particle B;
		public int Length;

		public void Constrain ()
		{
			Point p = A.Position;
			Vector d = B.Position - p;

			double distance = EstimateDistance (d, Length);
			//double distance = (Length + (d.X * d.X + d.Y * d.Y) / Length) / 2;
			//double distance = Math.Sqrt (d.X * d.X + d.Y * d.Y);
			double fraction = (distance - Length) / distance / 2;
			A.Position = p + d * fraction;
			B.Position = p + d * (1 - fraction);
		}

		protected static double EstimateDistance (Vector d, double r)
		{
			//approximate square roots
			return (r + (d.X * d.X + d.Y * d.Y) / r) / 2;
			//return Math.Sqrt (d.X * d.X + d.Y * d.Y);
		}
	}

	public class StringLink : Stick, IConstraint
	{
		/*
		public Particle A;
		public Particle B;
		public int Length;
		*/

		public StringLink (Particle a, Particle b, int length)
		{
			this.A = a;
			this.B = b;
			this.Length = length;
		}

		public void Constrain ()
		{
			Point p = A.Position;
			Vector d = B.Position - p;

			double distance = EstimateDistance (d, Length);

			if (distance < Length)
				return;

			double fraction = (distance - Length) / distance / 2;
			A.Position = p + d * fraction;
			B.Position = p + d * (1 - fraction);
		}
	}

	public class Spring
	{
		public Particle A;
		public Particle B;
		public int Length;

		public void AccumulateForces (Model model)
		{
			Point pos = A.Position;
			Vector d = B.Position - pos;

			double distance = Math.Sqrt (d.X*d.X + d.Y*d.Y);

			Vector u = d / distance;

			double displacement = distance - Length;

			A.Force += u * model.K * displacement;
			B.Force -= u * model.K * displacement;
		}
	}

	public class OffsetSpring
	{
		public Particle A;
		public Particle B;
		public Vector Offset;

		public OffsetSpring (Particle a, Particle b, Vector offset)
		{
			this.A = a;
			this.B = b;
			this.Offset = offset;
		}

		public void AccumulateForces (Model model)
		{
			//Point middle = (A.Position + B.Position) / 2;
			Point middle = new Point ((A.Position.X + B.Position.X) / 2, (A.Position.Y + B.Position.Y) / 2);

			Vector d = (middle - Offset / 2) - A.Position;

			A.Force += d * model.K;
			B.Force -= d * model.K;
		}
	}

	public class Spacer : Stick, IConstraint
	{
		public void Constrain ()
		{
			Point p = A.Position;
			Vector d = B.Position - p;

			double distance = EstimateDistance (d, Length);
			if (distance > Length)
				return;

			double fraction = (distance - Length) / distance / 2;
			A.Position = p + d * fraction;
			B.Position = p + d * (1 - fraction);
		}
	}

	public class Anchor : IConstraint
	{
		public Particle Particle;
		public Point Position;

		public void Constrain ()
		{
			if (Particle != null)
				Particle.Position = Position;
		}
	}

	/*
	public class Rectangle : Polygon
	{
	}
	*/

	public class Polygon : IConstraint
	{
		public List<Point> Points = new List<Point> ();
		public List<Vector> Normals = new List<Vector> ();
		public bool Enclosing;
		public Model Model;

		public bool Contains (Point point)
		{
			for (int i = 0 ; i != Points.Count ; i++) {
				Vector d = point - Points[i];

				if (Normals[i].X * d.X + Normals[i].Y * d.Y >= 0)
					return Enclosing;
			}

			return !Enclosing;
		}

		public void ReflectParticle (Particle n, double elasticity)
		{
			int edge = 0;
			double distance = Double.MinValue;
			Vector norm = new Vector (0, 0);

			for (int i = 0 ; i != Points.Count ; i++) {
				double d = Normals[i].X * (n.Position.X - Points[i].X) +
					Normals[i].Y * (n.Position.Y - Points[i].Y);

				if (d > distance) {
					distance = d;
					edge = i;
					norm = Normals[i];
				}
			}

			n.Position -= (1 + elasticity) * distance * norm;

			distance = norm.X * (n.PreviousPosition.X - Points[edge].X) +
					norm.Y * (n.PreviousPosition.Y - Points[edge].Y);

			n.PreviousPosition -= (1 + elasticity) * distance * norm;
		}

		public void Constrain ()
		{
			foreach (Particle n in Model.Particles)
				if (Contains (n.Position))
					ReflectParticle (n, Model.Elasticity);
		}

		//TODO: maybe this should be in Particle or elsewhere?
		/*
		public void Constrain (Particle n)
		{
			if (Contains (n.Position))
					ReflectParticle (n, Model.Elasticity);
		}
		*/

		public void Init ()
		{
			//Compute outward pointing normals
			Normals = new List<Vector> (Points.Count);
			for (int i = 0 ; i != Points.Count ; i++) {
				int j = (i + 1) % Points.Count;
				Vector d = Points[j] - Points[i];
				double length = Math.Sqrt (d.X*d.X + d.Y*d.Y);
				//Normals[i] = new Vector (-d.Y / length, d.X / length);
				Normals.Add (new Vector (-d.Y / length, d.X / length));
			}
		}
	}

	public class Offset : IConstraint
	{
		public List<Particle> Particles = new List<Particle> ();
		public Vector Delta;

		public Offset (Vector delta)
		{
			this.Delta = delta;
		}

		public void Constrain ()
		{
			Point pos = Point.Origin;

			for (int i = 0 ; i != Particles.Count ; i++)
				pos += Particles[i].Position - Point.Origin;

			//improve overloads for point/double?
			pos = pos / new Vector (Particles.Count, Particles.Count) - Delta * (Particles.Count - 1) / 2;

			for (int i = 0 ; i != Particles.Count ; i++)
				Particles[i].Position = pos + Delta * (double)i;
		}
	}
}
